package com.yingyi.user.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.yingyi.basic.constant.Identity;
import com.yingyi.basic.constant.RegisterMethod;
import com.yingyi.basic.exception.BusinessException;
import com.yingyi.basic.jwt.JwtUtils;
import com.yingyi.basic.jwt.RsaUtils;
import com.yingyi.basic.util.HttpUtil;
import com.yingyi.basic.util.JsonResult;
import com.yingyi.basic.util.Md5Utils;
import com.yingyi.basic.util.StrUtils;
import com.yingyi.org.mapper.EmployeeMapper;
import com.yingyi.system.domain.Menu;
import com.yingyi.user.constant.WxConstants;
import com.yingyi.user.domain.Logininfo;
import com.yingyi.user.domain.User;
import com.yingyi.user.domain.Wxuser;
import com.yingyi.user.dto.BinderDto;
import com.yingyi.user.dto.LoginDto;
import com.yingyi.user.mapper.LogininfoMapper;
import com.yingyi.user.mapper.UserMapper;
import com.yingyi.user.mapper.WxuserMapper;
import com.yingyi.user.service.ILogInService;
import com.yingyi.user.service.IUserService;
import com.yingyi.user.util.LoginData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Service
public class LogInServiceImpl implements ILogInService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private IUserService userService;

    @Autowired
    private EmployeeMapper employeeMapper;
    /**
     * 用户登录
     *  1. 用户登录信息的验证
     *       功能实现要点
     *           （1）可以使用用户名、电话、邮箱登录
     *           （2）考虑多个用户重名的情况 （查询的返回值为一个集合）
     *           （3）验证时对比所有的查询结果，判断是否有匹配的记录
     *
     *  2. 成功登录后数据的保存（token、用户信息（除去salt、password））
     * @param loginDto
     * @return
     */
    @Override
    public JsonResult account(LoginDto loginDto) throws BusinessException {
        String account = loginDto.getAccount();
        String password = loginDto.getPassword();
        Integer type = loginDto.getType();
        //非空验证
        if(StringUtils.isEmpty(account) || StringUtils.isEmpty(password) || type == null){
            throw new BusinessException("参数不正确，请确认后重新登录");
        }

        //登录信息验证
        List<Logininfo> logininfoList= logininfoMapper.queryByMessage(loginDto);
        if(logininfoList == null || logininfoList.isEmpty()){
            throw new BusinessException("用户名或密码不正确，请确认后重新登录");
        }

        //登录的用户的数据
        Logininfo logininfo = null;
        //验证密码
        for (Logininfo login : logininfoList) {
            //输入的密码进行加盐、加密再与数据库中的密码进行比较
            String salt = login.getSalt();
            String encryptedPwd = Md5Utils.encrypByMd5(salt.concat(password));
            if(login.getPassword().equals(encryptedPwd)) {
                //查询到匹配的员工信息
                logininfo = login;
                break;
            }
        }

        if(logininfo == null) {
            throw new BusinessException("密码不正确");
        }

        /*//登录信息的数据保存 token 用户信息
        String token = UUID.randomUUID().toString();

        //设置有效期30分钟 存储的格式key-value key使用token value使用logininfo
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);

        //密码、salt不用响应给前端
        logininfo.setPassword(null);
        logininfo.setSalt(null);

        HashMap<String, Object> map = new HashMap<>();

        map.put("token", token);
        map.put("logininfo", logininfo);*/

        //获取logininfo数据、权限、菜单数据响应到前端
        Map<String, Object> map = loginSuccessJwtHandler(logininfo);

        return JsonResult.me().setResultObj(map);
    }



    /**
     * 微信登录
     * 流程分析：
     *  1 通过code发送请求获取 token 返回数据是Json格式的
     *     使用工具类处理 Json格式
     *  2 根据判断OpenId查询用户是否已经绑定user账号
     *      如果绑定了user账号 则登录该账号 （类似用户登录）并将用户的登录信息响应回前台保存 【免密登录】
     *      如果没有绑定user账号，则需要将微信信息与一个我们系统的账号绑定
     *          将 access_token openid 作为参数响应到前台 用作请求微信资源获取用户信息的参数
     *
     *           需要发送第三次请求 从weChat获取用户的信息 将用户信息保存到t_wxuser表中，注意关联t_user的字段user_id
     *           将用户信息保存到t_user表中 同步到t_logininfo中
     *           注意：三张表的关联关系： t_wxuser  t_user t_logininfo
     *
     * @param code
     * @return
     */
    @Override
    public JsonResult weChatLogin(String code) throws BusinessException {

        //根据code发送请求获取token和openid 返回值weiJson格式的数据，使用fastJson中的方法处理
        /**
         * {
         * "access_token":"ACCESS_TOKEN",
         * "expires_in":7200,
         * "refresh_token":"REFRESH_TOKEN",
         * "openid":"OPENID",
         * "scope":"SCOPE",
         * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
         * }
         */
        if (code == null || code.isEmpty()) {
            throw new BusinessException("网络异常，请稍后重试");
        }

        String jsonStr = HttpUtil.httpGet(WxConstants.getAckUrl(code));
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        String access_token = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");


        //根据openid判断用户账号是否已经绑定user账号
        Wxuser dbWxUser =  wxuserMapper.queryByOpenId(openid);

        if(dbWxUser != null && dbWxUser.getUserId() != null){
            //绑定了user账号， 则登录user账号，将用户数据响应到前端【免密登录】

            //连表查询t_logininfo中的记录
            Logininfo logininfo = logininfoMapper.queryByUserId(dbWxUser.getUserId());
            /*//登录信息的数据保存 token 用户信息
            String token = UUID.randomUUID().toString();

            //设置有效期30分钟 存储的格式key-value key使用token value使用logininfo
            redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);

            //密码、salt不用响应给前端
            logininfo.setPassword(null);
            logininfo.setSalt(null);

            HashMap<String, Object> map = new HashMap<>();

            map.put("token", token);
            map.put("logininfo", logininfo);*/

            Map<String, Object> map = loginSuccessJwtHandler(logininfo);

            return JsonResult.me().setResultObj(map);

        } else {
            //没有绑定, 需要跳转页面绑定 - 响应给前端进行跳转，
            //将 access_token openid 作为参数响应到前台 用作请求微信资源获取用户信息的参数
            String params = "?access_token=" + access_token + "&openid=" + openid;

            return JsonResult.me().setMessage("绑定信息").setResultObj(params);

        }
    }


    /**
     *如果微信没有绑定user账号，则需要将微信信息与一个我们系统的账号绑定
     *  分析：
     *  1 如果有微信信息但没有user信息（t_wxuser中的t_user没有关联数据）需要在t_user和t_logininfo中新增记录并且通过外键关联起来
     *  2 如果没有微信信息（t_wxuser中没有数据）则
     *      需要发送第三次请求 从weChat获取用户的信息 将用户信息保存到t_wxuser表中，注意关联t_user的字段user_id
     *      再在t_user和t_logininfo中新增记录，通过外键关联起来
     *  将用户信息保存到t_user表中 同步到t_logininfo中
     *  注意：三张表的关联关系： t_wxuser  t_user t_logininfo
     * @param dto
     * @return
     */
    @Override
    public JsonResult weChatBinder(BinderDto dto) throws BusinessException {
        String openid = dto.getOpenid();
        String access_token = dto.getAccess_token();
        String phone = dto.getPhone();
        String verifyCode = dto.getVerifyCode();

        //数据有效性校验
        if(StringUtils.isEmpty(access_token)
            || StringUtils.isEmpty(verifyCode)
            || StringUtils.isEmpty(phone)
            || StringUtils.isEmpty(openid)
        ){
            throw new BusinessException("参数不正确，请检查后重试");
        }

        String key = phone + RegisterMethod.WECHAT_REGISTER;
        //获取存储在redis中的code（需要截取）
        String codeInRedis = redisTemplate.opsForValue().get(key).toString().split(":")[0];
        if(codeInRedis == null) {
            //验证码失效
            throw new BusinessException("验证码失效，请重新获取");
        }

        if(!codeInRedis.equalsIgnoreCase(verifyCode)){
            //验证码不正确
            throw new BusinessException("验证码不正确，请重新输入");
        }

        //获取WxUser - 发送第三个请求 - json字符串
        String jsonStr = HttpUtil.httpGet(WxConstants.getUserUrl(access_token, openid));
        Wxuser wxuser = jsonStr2WxUser(jsonStr, openid);

        //验证手机号是否已经注册，创建自己系统的账号t_user和t_logininfo
        User dbUser = userMapper.queryByPhone(phone);

        if(dbUser == null){
            //手机号没有注册， 需要在t_user和t_longininf中增加关联记录
            dbUser =  phone2User(phone, wxuser);

            //调用UserServiceImpl中覆写的insert方法
            userService.insert(dbUser);
        }

        //设置t_wxuser中关联t_user的字段user_id
        wxuser.setUserId(dbUser.getId());

        /**
         * 此处使用insert还是update
         *  当存在wxuser时应该使用update
         *  不存在wxuser时应该使用insert
         */

        if(wxuser.getId() == null){
            wxuserMapper.insert(wxuser);
        } else {
            wxuserMapper.update(wxuser);
        }

        //做免密登录

        //获取登录信息
        Logininfo logininfo = logininfoMapper.queryByUserId(dbUser.getId());

        /*//登录信息的数据保存 token 用户信息
        String token = UUID.randomUUID().toString();

        //设置有效期30分钟 存储的格式key-value key使用token value使用logininfo
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);

        //密码、salt不用响应给前端
        logininfo.setPassword(null);
        logininfo.setSalt(null);

        HashMap<String, Object> map = new HashMap<>();

        map.put("token", token);
        map.put("logininfo", logininfo);*/

        Map<String, Object> map = loginSuccessJwtHandler(logininfo);

        return JsonResult.me().setResultObj(map);
    }

    private User phone2User(String phone, Wxuser wxuser) {
        User user = new User();
        /**
         * id
         * username
         * phone
         * email
         * salt
         * password
         * state
         * age
         * createtime
         * headImg
         * logininfo_id
         */
        user.setPhone(phone);
        user.setUsername(wxuser.getNickname());
        user.setHeadimg(wxuser.getHeadimgurl());

        /**
         * 设置盐值与密码
         */
        String salt = StrUtils.getComplexRandomString(32);

        //使用6位的随机密码
        String randomPwd = StrUtils.getRandomString(6);

        String pwdEncrypted = Md5Utils.encrypByMd5(salt + randomPwd);

        user.setSalt(salt);
        user.setPassword(pwdEncrypted);

        return user;
    }

    /**
     * 将json格式的用户的微信信息转换为Wxuser对象
     * @param jsonStr
     * @return
     */
    private Wxuser jsonStr2WxUser(String jsonStr, String openid) {
        Wxuser dbWxUser = wxuserMapper.queryByOpenId(openid);

        /**
         * 考虑存在wxuser，但没有绑定user信息的情况
         * wxuser表中的t_userId为null
         * 直接使用数据库中的记录，不需要再创建新的对象
         */
        if(dbWxUser != null){
            return dbWxUser;
        }

        Wxuser wxuser = new Wxuser();
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);

        /**
         * {
         * "openid":"OPENID",
         * "nickname":"NICKNAME",
         * "sex":1,
         * "province":"PROVINCE",
         * "city":"CITY",
         * "country":"COUNTRY",
         * "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
         * "privilege":[
         * "PRIVILEGE1",
         * "PRIVILEGE2"
         * ],
         * "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
         *
         * }
         *
         * id
         * openid
         * nickname
         * sex
         * address
         * headimgurl
         * unionid
         * user_id
         */
        wxuser.setOpenid(jsonObject.getString("openid"));
        wxuser.setNickname(jsonObject.getString("nickname"));
        wxuser.setSex(jsonObject.getInteger("sex"));
        wxuser.setAddress(
                jsonObject.getString("country") + ' ' +
                jsonObject.getString("province") + ' ' +
                jsonObject.getString("city")
        );
        wxuser.setHeadimgurl(jsonObject.getString("headimgurl"));
        wxuser.setUnionid(jsonObject.getString("unionid"));

        return wxuser;
    }


    /**
     * 登录成功后将用户信息，菜单信息，权限信息保存为jwtToken(加密),用于后续前端发送请求时携带，后台进行验证使用
     * 并且使用一个Map响应到前端(jwtToken logininfo permissions menus)
     * @param logininfo
     * @return
     */
    private Map<String, Object> loginSuccessJwtHandler(Logininfo logininfo) {
        //创建LoginData对象过私钥加密
        Map<String, Object> map = new HashMap<>();
        LoginData loginData = new LoginData();
        //不需要向前端响应用户的password和salt信息
        logininfo.setPassword(null);
        logininfo.setSalt(null);

        loginData.setLogininfo(logininfo);

        try {
            //1.获取logininfo
            if(logininfo.getType().equals(Identity.EMPLOYEE)){
                //2.获取当前登陆人的所有权限 - sn
                List<String> permissions = employeeMapper.getPermissionsByLogininfoId(logininfo.getId());
                loginData.setPermissions(permissions);
                //3.获取当前登陆人的所有菜单
                List<Menu> menus = employeeMapper.getMenusByLogininfoId(logininfo.getId());
                loginData.setMenus(menus);

                //将当前登陆人的权限和菜单添加到map - 响应给前端
                map.put("permissions", permissions);
                map.put("menus", menus);

            }
            //4.通过私钥对登录信息进行加密 - jwtToken串
            PrivateKey privateKey = RsaUtils.getPrivateKey(RsaUtils.class.getClassLoader().getResource("auth_rsa.pri").getFile());
            //将登陆人信息加密得到jwtToken串
            String jwtToken = JwtUtils.generateTokenExpireInMinutes(loginData, privateKey, 30);

            //5.装到map返回
            map.put("token",jwtToken);
            map.put("logininfo",logininfo);
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}
